package se.webbzon.boltzmann.minigame.sandbox;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;

import se.webbzon.boltzmann.configuration.Graphics;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldcamera.WorldCameraLayer;
import se.webbzon.oschi01.worldcamera.WorldCameraPane;
import se.webbzon.oschi01.worldobject.WorldObject;

public class DefaultSandBox implements SandBox {
	
	// The 2-dimensional depth of this sand box.
	private static final int startDepth = 1000, endDepth = 0;
	
	// The maximal distance for dropping items in containers
	private static final double maxDropDistance = 32;
	
	// The offset of the box relative to the center of the heads-up display
	private static final int xOffset = 0, yOffset = -40;
	
	// The world object displaying this sand box
	private final WorldObject bg, fg;
	
	// The layer of the sand box
	private final WorldCameraLayer layer;
	
	// A collection of the sand box items registered for this sand box 
	private final Collection<SandBoxItem> items;
	
	// A collections of all the listeners to this sand box. 
	private final Collection<SandBoxListener> listeners;
	
	// The score of this sand box
	private SandBoxScore score;
	
	// The info of this sand box
	private SandBoxInfo info;
	
	// The background color of this sand box
	private Color background = Color.lightGray;
	
	/*============================================================
	Constructors
	============================================================*/

	public DefaultSandBox(WorldArea world) {
		bg = new Background();	
		fg = new Foreground();
		layer = new WorldCameraLayer(new Dimension(624,264));
		items = new ArrayList<SandBoxItem>();
		listeners = new ArrayList<SandBoxListener>();
		world.createInstance(bg);
		world.createInstance(fg);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	@Override public SandBoxInfo getInfo() {
		return info;
	}

	@Override public void setInfo(SandBoxInfo info) {
		this.info = info;
	}
	
	@Override public SandBoxScore getScore() {
		return score;
	}

	@Override public void setScore(SandBoxScore score) {
		this.score = score;
	}
	
	@Override public Color getBackgroundColor() {
		return background;
	}
	
	@Override public void setBackgroundColor(Color background) {
		this.background = background;
	}
	
	@Override public WorldArea getWorld() {
		return bg.getWorld();
	}
	
	@Override public boolean addListener(SandBoxListener listener) {
		return listeners.add(listener);
	}
	
	@Override public boolean removeListener(SandBoxListener listener) {
		return listeners.remove(listener);
	}
	
	@Override public void clearListeners() {
		listeners.clear();
	}
	
	@Override public void getBoundary(Rectangle rect) {
		Dimension d = new Dimension();
		Graphics.instance().getDimension(d);
		Dimension l = new Dimension();
		layer.getDimension(l);
		int x = (d.width - l.width) / 2 + xOffset;
		int y = (d.height - l.height) / 2 + yOffset;
		rect.setBounds(x, y, l.width, l.height);
	}
	
	@Override public boolean isWithinSandbox(SandBoxItem item) {
		return isWithinSandbox(item.getX(),item.getY());
	}
	
	@Override public boolean isWithinSandbox(double x, double y) {
		Rectangle rect = new Rectangle();
		getBoundary(rect);
		return rect.contains(x, y);
	}
	
	@Override public boolean addItem(SandBoxItem item) {
		if (items.add(item)) {
			item.onAttach(this);
			return true;
		} else
			return false;
	}
	
	@Override public boolean removeItem(SandBoxItem item) {
		if (items.remove(item)) {
			item.onDetach(this);
			return true;
		} else
			return false;
	}
	
	@Override public void clearItems() {
		for (SandBoxItem item : items.toArray(new SandBoxItem[0])) {
			item.onDetach(this);
		}
		items.clear();
	}
	
	@Override public SandBoxContainer drop(SandBoxItem item) {
		final double xc = item.getX();
		final double yc = item.getY();
		for (SandBoxItem i : items) {
			if (i instanceof SandBoxContainer) {
				SandBoxContainer c = (SandBoxContainer) i;
				final double dx = xc - c.getX();
				final double dy = yc - c.getY();
				if (Math.hypot(dx, dy) < maxDropDistance) {
					if (c.putItem(this,item)) {
						notifyDrop(item,c);
						return c;
					} else {
						notifyDrop(item,null);
						return null;
					}
				}
			}
		}
		if (item.droppable())
			notifyDrop(item,null);
		return null;
	}
	
	@Override public SandBoxItem pick(double x, double y) {
		SandBoxItem pickedItem = null;
		boolean pickFromContainer = false;
		double closestDistance = maxDropDistance;
		for (SandBoxItem item : items) {
			if (item instanceof SandBoxContainer) {
				SandBoxContainer container = (SandBoxContainer) item;
				if (!container.isEmpty() || container.draggable()) {
					double d = Math.hypot(x - container.getX(), y - container.getY());
					if (d < closestDistance) {
						closestDistance = d;
						pickedItem = item;
						pickFromContainer = !container.isEmpty();
					}
				}
			}  else if (item.draggable()) {
				double d = Math.hypot(x - item.getX(), y - item.getY());
				if (d < closestDistance) {
					closestDistance = d;
					pickedItem = item;
					pickFromContainer = false;
				}
			}
		} 
		
		if (pickedItem == null) {
			notifyInvoke(x,y);
			return null;
		} else if (pickFromContainer) {
			SandBoxContainer container = (SandBoxContainer) pickedItem;
			pickedItem = container.pickItem(this);
			notifyPickup(pickedItem,container);
			return pickedItem;
		} else {
			notifyPickup(pickedItem,null);
			return pickedItem;
		}
	}

	/*============================================================
	Private Methods
	============================================================*/
	
	/** Notifies a pickup event for all the listeners. **/
	private final void notifyPickup(SandBoxItem item, SandBoxContainer container) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onPick(this, item, container);
	}
	
	/** Notifies a drop event for all the listeners. **/
	private final void notifyDrop(SandBoxItem item, SandBoxContainer container) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onDrop(this, item, container);
	}
	
	/** Notifies an invoke event for all the listeners. **/
	private final void notifyInvoke(double x, double y) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onInvoke(this, x, y);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** The background of the sand box. **/
	private final class Background extends WorldObject {

		public Background() {
			super(WorldObject.onPostDraw, startDepth);
		}
		
		@Override public void onPostDraw(WorldArea world, WorldCameraPane hud) {
			Dimension l = new Dimension();
			layer.clear();
			layer.getDimension(l);
			layer.setColor(background);
			layer.setTransparency(0.9f);
			layer.fillRect(1, 1, l.width-2, l.height-2);
			layer.setTransparency(1);
			hud.pushLayer(layer);
		}
		
	}
	
	/** The foreground of the sand box. **/
	private final class Foreground extends WorldObject {

		public Foreground() {
			super(WorldObject.onPostDraw, endDepth);
		}
		
		@Override public void onPostDraw(WorldArea world, WorldCameraPane hud) {
			Dimension l = new Dimension(), d = new Dimension();
			layer.getDimension(l);
			//layer.clip(0, 0, 160, 160);
			layer.setColor(Color.blue);
			layer.drawRect(0, 0, l.width-1, l.height-1);
			hud.popLayer();
			hud.getLayer().getDimension(d);
			int x = (d.width - l.width) / 2 + xOffset;
			int y = (d.height - l.height) / 2 + yOffset;
			hud.drawLayer(x, y, layer);
		}
		
	}
}
